/*
    Nios-sim - one simple NIOSII simulator only for personal interest and fun.
    Copyright (C) 2010  chysun2000@gmail.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

#include "public.h"
#include "niosii.h"
#include "io_device.h"
#include "simulator.h"

static int32_t simulator_mode = SIM_MODE;

static void modify_pc(struct NIOS_CPU * cpu)
{
	cpu->pc += 4;
}

static void set_init_pc(struct NIOS_CPU * cpu, uint32_t pc)
{
	cpu->pc = pc;
}

static void quit_debug_mode(struct NIOS_CPU * cpu)
{
	simulator_mode = SIM_MODE;
	cpu->mode = NORMAL_MODE;
	cpu->break_pc = 0;
}

int32_t get_run_mode(void)
{
	return simulator_mode;
}

void set_debug_mode(struct NIOS_CPU * cpu)
{
	simulator_mode = DEBUG_MODE;
	cpu->break_pc = 0;
	cpu->mode = NORMAL_MODE;
}

#define CMD_CHAR_SIZE (256)

static char input_char[CMD_CHAR_SIZE] = {0};
enum {
	DISP_CURR = 0x01,
	DISP_NEXT,
	DUMP_REG,
	HELP,
	GO,
	DISP_PC,
	SET_BREAK,
	QUIT,
	DUMP_MEM,
	CALL_STACK,
	QUIT_SIM
};

static const struct debug_command debug_cmds[] = {
	{"c", "disp_curr", "display the contents of current code", DISP_CURR},
	{"n", "disp_next", "display the contents of next code", DISP_NEXT},
	{"r", "dump_reg", "display the contents of register", DUMP_REG},
	{"h", "help", "display the help information", HELP},
	{"g", "go", "continue executing", GO},
	{"p", "disp_pc", "display current PC Value", DISP_PC},
	{NULL, "break", "set break address.For example:\n\t(1)break 08000010\n\t(2)break func=function name", SET_BREAK},
	{"qd", "quit_debug","quit debug mode", QUIT},
	{NULL, "dump", "dump memory",DUMP_MEM},
	{"cs", "call_stack", "dump call stack", CALL_STACK},
	{"qs", "quit_sim", "quit simulator", QUIT_SIM}
};

#define CMD_COUNT (sizeof(debug_cmds) / sizeof(debug_cmds[0]))

static void disp_help(void)
{
	int32_t i;
	printf("\n[HELP INFORMATION]\n");
	printf("==============================================\n");
	for (i=0;i<CMD_COUNT;i++){
		printf("[%d] \"%s\" or \"%s\"\n    %s\n",
			debug_cmds[i].code, 
			debug_cmds[i].short_cmd, 
			debug_cmds[i].long_cmd, 
			debug_cmds[i].desc);
	}
	printf("==============================================\n");
}

static int32_t get_cmd_code(char * input_char)
{
	int32_t ret_val = 0;
	int32_t i = 0;
	
	if (strlen(input_char) == 0){
		return GO;
	}

	for(i=0;i<CMD_COUNT;i++){
		if (debug_cmds[i].short_cmd != NULL 
			&& strcmp(input_char, debug_cmds[i].short_cmd) == 0){
			ret_val = debug_cmds[i].code;
			break;
		}

		if (debug_cmds[i].long_cmd != NULL 
			&& strcmp(input_char, debug_cmds[i].long_cmd) == 0){
			ret_val = debug_cmds[i].code;
			break;
		}

		if (debug_cmds[i].short_cmd == NULL && debug_cmds[i].long_cmd != NULL
			&& strstr(input_char, debug_cmds[i].long_cmd) != NULL){
			ret_val = debug_cmds[i].code;
			break;
		}
	}
	return ret_val;
}

static void quit_simulator(void)
{
	simulator_mode = EXIT_MODE;
}

static void dump_call_stack(void);
static const char *DEBUG_PMT = "(dbg)";
char break_func_name[256] = {0};
static int32_t exec_this_func(char * func_name);
static void debug_mode(struct NIOS_CPU * cpu, uint32_t code, uint32_t exec_ret)
{
	int32_t input_key = 0;
	int32_t index = 0;

	if (cpu->mode == BREAK_MODE){
		if (cpu->break_pc == cpu->pc){
			cpu->break_pc = 0;
			cpu->mode = SINGLE_STEP;
		}
		else if (exec_this_func(break_func_name) == SIM_TRUE){
			cpu->break_pc = 0;
			cpu->mode = SINGLE_STEP;
		}
		else {
			return;
		}
	}

	for (;;){
		memset(input_char, 0x00, CMD_CHAR_SIZE);
		index = 0;
		printf("%s-(PC:%08X)#",DEBUG_PMT,cpu->pc);

		while(1){
			input_key = getchar();
			if (input_key != '\n' && index < CMD_CHAR_SIZE){
				input_char[index++] = (char)input_key;
			}
			else {
				break;
			}
		}
		
		input_key = get_cmd_code(input_char);
		switch(input_key){
		case DISP_CURR:
			dump_curr_code(code);
			break;
		case DISP_NEXT:
			dump_next_code(cpu);
			break;
		case DUMP_REG:
			dump_register(cpu);
			break;
		case HELP:
			disp_help();
			break;
		case GO:
			return;
		case DISP_PC:
			dump_pc(cpu);
			break;
		case SET_BREAK:
			set_break(cpu, input_char);
			break;
		case DUMP_MEM:
			dump_mem(cpu, input_char);
			break;
		case QUIT:
			quit_debug_mode(cpu);
			return;
		case QUIT_SIM:
			quit_debug_mode(cpu);
			quit_simulator();
			return;
		case CALL_STACK:
			dump_call_stack();
			break;
		default:
			printf("INVALID COMMAND\n");
			continue;
		}
	}
}

static void sigint_handler(int sig)
{
	int32_t input = '\0';
	struct NIOS_CPU * cpu = get_nios_cpu();
	
	signal(sig, SIG_IGN);

	while(1){
		printf("\nPlease select what you want?\n");
		printf("Q for Quit Simulator\n");
		printf("D for enter Debug Mode\n");
		printf("R for Resume the simulating\n");
		printf("N for do Nothing\n#");
		
		input = getchar();
		if ((char)input == '\n'){
			goto handler_out;
		}
		
		switch((char)input){
			case 'Q':
				quit_debug_mode(cpu);
				simulator_mode = EXIT_MODE;
				return;
			case 'D':
				set_debug_mode(cpu);
				goto handler_out;
			case 'R':
				quit_debug_mode(cpu);
				goto handler_out;
			case 'N':
				goto handler_out;
			default:
				continue;
		}
	}

handler_out:	
	signal(SIGINT, sigint_handler);
}

static void sigsegv_handler(int sig)
{
	struct NIOS_CPU * cpu = get_nios_cpu();
	
	signal(sig, SIG_IGN);
	printf("\n--------------Segmentation fault------------------\n");
	dump_pc(cpu);
	dump_register(cpu);
	printf("----------------------------------------------------\n");
	dump_call_stack();
	_exit(0);
}

static void register_signal_handler(void)
{
	signal(SIGINT, sigint_handler);
	signal(SIGSEGV, sigsegv_handler);
}

#define CS_LEN (32)
struct call_stack {
	struct symbol_obj * stack[CS_LEN];
	uint32_t pos;
};

static struct call_stack cs = {
	.stack = {NULL},
	.pos = 0,
};

static int32_t exec_this_func(char * func_name)
{
	struct symbol_obj * obj = NULL;

	if (cs.pos == 0){
		obj = cs.stack[0];
	}
	else{
		obj = cs.stack[cs.pos-1];
	}

	if (obj != NULL){
		if (strcmp(obj->sym_name, func_name) == 0){
			return SIM_TRUE;
		}
	}
	
	return SIM_FALSE;
}
static void dump_call_stack(void);
static void record_call_stack(uint32_t addr)
{
	struct symbol_obj * obj = NULL;
	
	obj = get_symbol(addr);
	if (obj != NULL){
		if (cs.pos == 0){
			cs.stack[cs.pos] = obj;
			cs.pos ++;
		}
		else {
			if (cs.stack[cs.pos-1] != obj){
				cs.stack[cs.pos] = obj;
				cs.pos ++;
				if (cs.pos == CS_LEN){
					cs.pos = 0;
				}
			}
		}
	}
}

static void dump_call_stack(void)
{
	int32_t i = 0;
	struct symbol_obj * obj = NULL;

	printf("\n============================================================\n");
	printf("[Function Call Trace]\n");
	printf("============================================================\n");
	for(i=0;i<CS_LEN;i++){
		if(cs.stack[i] != NULL){
		    obj = cs.stack[i];
			if (cs.pos == (i + 1)){
				printf("[%03d] %s (0x%08x)**\n", i, obj->sym_name, obj->addr);
			}
			else {
				printf("[%03d] %s (0x%08x)\n", i, obj->sym_name, obj->addr);
			}
		}
	}
	printf("============================================================\n");
}

void simulating(void)
{
	struct image_info * info = get_image_info();
	struct NIOS_CPU * cpu = NULL;
	uint32_t code = 0;
	uint32_t exec_result = 0;
	
	/* register the segment fault and Ctrl-C handler */
	register_signal_handler();
	
	/* init cpu*/
	reset_cpu();
	cpu = get_nios_cpu();
	set_init_pc(cpu, info->entry_addr); /* Set the init value of PC */
	cpu = get_nios_cpu();

	/* init device modules */
	init_devices();
	/* init the boot parameter setting for u-boot */
	init_cmdline(cpu);
	init_initrd(cpu);

	/* main loop to simulating the execute of NIOSII */
	printf("Enter simulating ..................\n");
	while(1){
		/* Get the instruction */
		code = get_instruct(cpu, info->mem_base, info->base_addr);

		/* Judge whether into debug mode */
		if (get_run_mode() == DEBUG_MODE){
			if (cpu->mode == NORMAL_MODE){
				cpu->mode = SINGLE_STEP;
			}
			trace_pc(cpu);
			debug_mode(cpu, code, exec_result);
		}
		else if(get_run_mode() == EXIT_MODE) {
			printf("NIOS SIMULATOR EXIT!\n");
			return;
		}
		/* Execute the code */
		exec_result = execute(code);
		if (exec_result == PC_INC_NORMAL){
			modify_pc(cpu);
		}
		else {
			if(get_run_mode() == DEBUG_MODE){
				record_call_stack(cpu->pc);
			}
		}
		
		/* Simulate the hardware running. */
		hw_simulating();
		/* judge whether IRQ or exception happen, if yes, then handle it.*/
		handle_irq_exception(cpu);
	}
}

/*----------------------------------------------------------------------------*/


